#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

#include "./esp-idf-ssd1306/ssd1306.h"
#include "./esp-idf-ssd1306/font8x8_basic.h"

#include "./oled_drv_intf.h"
#include "Custom_Config.h"

/*
 You have to set this config value with menuconfig
 CONFIG_INTERFACE

 for i2c
 CONFIG_MODEL
 CONFIG_SDA_GPIO
 CONFIG_SCL_GPIO
 CONFIG_RESET_GPIO

 for SPI
 CONFIG_CS_GPIO
 CONFIG_DC_GPIO
 CONFIG_RESET_GPIO
*/

#define tag "SSD1306"
#define CONFIG_I2C_INTERFACE (1)
#define CONFIG_SSD1306_128x64 (1)
#define CONFIG_MODEL (0)
#define CONFIG_SDA_GPIO (SDA_PIN)
#define CONFIG_SCL_GPIO (SCL_PIN)
#define CONFIG_RESET_GPIO (0)

SSD1306_t dev;
uint64_t img[16*8];

# define LINE_LENGTH (16)


void oled_log(const char * logstr, int line) {
	if (dev._address != 0 && logstr != nullptr) {
		char one_line[LINE_LENGTH] = {0};
		size_t log_len = strlen(logstr);
		size_t cp_len = LINE_LENGTH > log_len ? LINE_LENGTH : log_len;
		memset(one_line, 0, LINE_LENGTH);
		strncpy(one_line, logstr, cp_len);
		ssd1306_display_text(&dev, line, one_line, LINE_LENGTH, false); //clear
	}
}


#define	GET_BIT(x, bit)	((x & (1 << bit)) >> bit)

void mode2Img(uint8_t * rose_flower) {
	uint64_t tmpImg64 = 0;
	for (size_t i = 0; i < 16*8; i++)
	{
		int col = i % 16;
		int row = i / 16;
		uint8_t reg_rose[8] = {0};
		for (size_t innerline = 0; innerline < 8; innerline++)
		{
			reg_rose[innerline] = rose_flower[(row * 8 + innerline) * 16 + col];	//start
		}
		
		for (size_t j = 0; j < 64; j++)
		{
			int lcol = j % 16;
			int lrow = j / 16;
			int rose_bit_id = GET_BIT(reg_rose[lrow], (7 - lcol));
			/* code */
			tmpImg64 = tmpImg64|rose_bit_id;
		}
		img[i] = tmpImg64;
		tmpImg64 = 0;
	}
	
}

void drawImg() {
	// init data
	#if 0
	for (int i = 0; i< 8; i++) {
		for (int j = 0; j< 16; j++) {
			img[i * 16 + j] = 0xffffffff;
		}
	}
	#endif
	memcpy(img, rose_flower, sizeof(uint64_t) * 16 * 8);

	#if 1
	mode2Img(rose_flower);
	uint8_t seg8_img[8] = {0};
	for (int page = 0; page < 8; page++) {
		for (int seg8 = 0; seg8 < 16; seg8++) {
			uint8_t * imgptr = (uint8_t *)&img[page * 16 + seg8];
			ssd1306_display_image(&dev, page, seg8, imgptr, 8);
		}
	}
	#endif

	//display
	#if 0
	static int position = 0;
	static int position_x = 0;	// < 128
	static int position_y = 0;	// < 64
	position_x = position / 128;
	position_y = position % 128;

	uint64_t seg64_img[16] = {0};
	for (int page = 0; page < 8; page++) {
		for (int seg64_col = 0; seg64_col < 128; seg64_col += 8) {
			for (size_t i = 0; i < 64; i++)
			{
				/* code */
				uint64_t temp_img = ((uint64_t)0x1) << i;
				ssd1306_display_image(&dev, page, seg64_col, (uint8_t *)&(temp_img), 8);
				vTaskDelay(100 / portTICK_PERIOD_MS);
			}
		}
	}
	#endif

	#if 0
	uint8_t seg8_img[8] = {0};
	for (int page = 0; page < 8; page++) {
		for (int seg8 = 0; seg8 < 16; seg8++) {
			uint8_t * imgptr = (uint8_t *)&img[page * 16 + seg8];
			int seg = seg8 * 8;
			for (int line = 0; line < 8; line++) {
				int rose_location = (page + line) * 16 + seg8;
				seg8_img[line] = rose_flower[rose_location];
			}
			ssd1306_display_image(&dev, page, seg, seg8_img, 8);
		}
	}
	#endif

	#if 0
	uint8_t seg8_img[128] = {0};
	for (int page = 0; page < 8; page++) {
		for (int id = 0; id < 128; id++) {
			int rose_location = page * 128 + (id % 8 *16) + id / 8;
			// int rose_location = page * 128 + id;
			seg8_img[id] = rose_flower[rose_location];
		}
		ssd1306_display_image(&dev, page, 0, seg8_img, 128);
	}
	#endif
	#if 0
	uint8_t seg8_img[128] = {0};
	ssd1306_display_image(&dev, 0, 0, rose_flower, 128 * 8);
	#endif

	vTaskDelay(100 / portTICK_PERIOD_MS);
}

void oled_anim(int i) {

	//
	drawImg();

	// Display Count Down
	#if 0
	int top = 1;
	uint8_t image[24];

	memset(image, 0, sizeof(image));
	ssd1306_display_image(&dev, top, (6*8-1), image, sizeof(image));
	ssd1306_display_image(&dev, top+1, (6*8-1), image, sizeof(image));
	ssd1306_display_image(&dev, top+2, (6*8-1), image, sizeof(image));
	for(int font=0x33;font>0x30;font--) {
		memset(image, 0, sizeof(image));
		ssd1306_display_image(&dev, top+1, (7*8-1), image, 8);
		memcpy(image, font8x8_basic_tr[font], 8);
		if (dev._flip) ssd1306_flip(image, 8);
		ssd1306_display_image(&dev, top+1, (7*8-1), image, 8);
		vTaskDelay(1000 / portTICK_PERIOD_MS);
	}
	#endif
}

void oled_init(void)
{
#if CONFIG_I2C_INTERFACE
	ESP_LOGI(tag, "INTERFACE is i2c");
	ESP_LOGI(tag, "CONFIG_SDA_GPIO=%d",CONFIG_SDA_GPIO);
	ESP_LOGI(tag, "CONFIG_SCL_GPIO=%d",CONFIG_SCL_GPIO);
	ESP_LOGI(tag, "CONFIG_RESET_GPIO=%d",CONFIG_RESET_GPIO);
	i2c_master_init_oled(&dev, CONFIG_SDA_GPIO, CONFIG_SCL_GPIO, CONFIG_RESET_GPIO);
#endif // CONFIG_I2C_INTERFACE

#if CONFIG_SPI_INTERFACE
	ESP_LOGI(tag, "INTERFACE is SPI");
	ESP_LOGI(tag, "CONFIG_MOSI_GPIO=%d",CONFIG_MOSI_GPIO);
	ESP_LOGI(tag, "CONFIG_SCLK_GPIO=%d",CONFIG_SCLK_GPIO);
	ESP_LOGI(tag, "CONFIG_CS_GPIO=%d",CONFIG_CS_GPIO);
	ESP_LOGI(tag, "CONFIG_DC_GPIO=%d",CONFIG_DC_GPIO);
	ESP_LOGI(tag, "CONFIG_RESET_GPIO=%d",CONFIG_RESET_GPIO);
	spi_master_init(&dev, CONFIG_MOSI_GPIO, CONFIG_SCLK_GPIO, CONFIG_CS_GPIO, CONFIG_DC_GPIO, CONFIG_RESET_GPIO);
#endif // CONFIG_SPI_INTERFACE

#if CONFIG_FLIP
	dev._flip = true;
	ESP_LOGW(tag, "Flip upside down");
#endif

#if CONFIG_SSD1306_128x64
	ESP_LOGI(tag, "Panel is 128x64");
	ssd1306_init(&dev, 128, 64);
#endif // CONFIG_SSD1306_128x64
#if CONFIG_SSD1306_128x32
	ESP_LOGI(tag, "Panel is 128x32");
	ssd1306_init(&dev, 128, 32);
#endif // CONFIG_SSD1306_128x32

	ssd1306_clear_screen(&dev, false);
	ssd1306_contrast(&dev, 0xff);
}

void oled_test(void)
{
	int center, top, bottom;
	char lineChar[20];
	oled_init();

#if CONFIG_SSD1306_128x64
	top = 2;
	center = 3;
	bottom = 8;
	ssd1306_display_text(&dev, 0, "SSD1306 128x64", 14, false);
	ssd1306_display_text(&dev, 1, "ABCDEFGHIJKLMNOP", 16, false);
	ssd1306_display_text(&dev, 2, "abcdefghijklmnop",16, false);
	ssd1306_display_text(&dev, 3, "Hello World!!", 13, false);
	ssd1306_clear_line(&dev, 4, true);
	ssd1306_clear_line(&dev, 5, true);
	ssd1306_clear_line(&dev, 6, true);
	ssd1306_clear_line(&dev, 7, true);
	ssd1306_display_text(&dev, 4, "SSD1306 128x64", 14, true);
	ssd1306_display_text(&dev, 5, "ABCDEFGHIJKLMNOP", 16, true);
	ssd1306_display_text(&dev, 6, "abcdefghijklmnop",16, true);
	ssd1306_display_text(&dev, 7, "Hello World!!", 13, true);
#endif // CONFIG_SSD1306_128x64

#if CONFIG_SSD1306_128x32
	top = 1;
	center = 1;
	bottom = 4;
	ssd1306_display_text(&dev, 0, "SSD1306 128x32", 14, false);
	ssd1306_display_text(&dev, 1, "Hello World!!", 13, false);
	ssd1306_clear_line(&dev, 2, true);
	ssd1306_clear_line(&dev, 3, true);
	ssd1306_display_text(&dev, 2, "SSD1306 128x32", 14, true);
	ssd1306_display_text(&dev, 3, "Hello World!!", 13, true);
#endif // CONFIG_SSD1306_128x32
	vTaskDelay(3000 / portTICK_PERIOD_MS);
	
	// Display Count Down
	uint8_t image[24];
	memset(image, 0, sizeof(image));
	ssd1306_display_image(&dev, top, (6*8-1), image, sizeof(image));
	ssd1306_display_image(&dev, top+1, (6*8-1), image, sizeof(image));
	ssd1306_display_image(&dev, top+2, (6*8-1), image, sizeof(image));
	for(int font=0x39;font>0x30;font--) {
		memset(image, 0, sizeof(image));
		ssd1306_display_image(&dev, top+1, (7*8-1), image, 8);
		memcpy(image, font8x8_basic_tr[font], 8);
		if (dev._flip) ssd1306_flip(image, 8);
		ssd1306_display_image(&dev, top+1, (7*8-1), image, 8);
		vTaskDelay(1000 / portTICK_PERIOD_MS);
	}
	
	// Scroll Up
	ssd1306_clear_screen(&dev, false);
	ssd1306_contrast(&dev, 0xff);
	ssd1306_display_text(&dev, 0, "---Scroll  UP---", 16, true);
	//ssd1306_software_scroll(&dev, 7, 1);
	ssd1306_software_scroll(&dev, (dev._pages - 1), 1);
	for (int line=0;line<bottom+10;line++) {
		lineChar[0] = 0x01;
		sprintf(&lineChar[1], " Line %02d", line);
		ssd1306_scroll_text(&dev, lineChar, strlen(lineChar), false);
		vTaskDelay(500 / portTICK_PERIOD_MS);
	}
	vTaskDelay(3000 / portTICK_PERIOD_MS);
	
	// Scroll Down
	ssd1306_clear_screen(&dev, false);
	ssd1306_contrast(&dev, 0xff);
	ssd1306_display_text(&dev, 0, "--Scroll  DOWN--", 16, true);
	//ssd1306_software_scroll(&dev, 1, 7);
	ssd1306_software_scroll(&dev, 1, (dev._pages - 1) );
	for (int line=0;line<bottom+10;line++) {
		lineChar[0] = 0x02;
		sprintf(&lineChar[1], " Line %02d", line);
		ssd1306_scroll_text(&dev, lineChar, strlen(lineChar), false);
		vTaskDelay(500 / portTICK_PERIOD_MS);
	}
	vTaskDelay(3000 / portTICK_PERIOD_MS);

	// Page Down
	ssd1306_clear_screen(&dev, false);
	ssd1306_contrast(&dev, 0xff);
	ssd1306_display_text(&dev, 0, "---Page	DOWN---", 16, true);
	ssd1306_software_scroll(&dev, 1, (dev._pages-1) );
	for (int line=0;line<bottom+10;line++) {
		//if ( (line % 7) == 0) ssd1306_scroll_clear(&dev);
		if ( (line % (dev._pages-1)) == 0) ssd1306_scroll_clear(&dev);
		lineChar[0] = 0x02;
		sprintf(&lineChar[1], " Line %02d", line);
		ssd1306_scroll_text(&dev, lineChar, strlen(lineChar), false);
		vTaskDelay(500 / portTICK_PERIOD_MS);
	}
	vTaskDelay(3000 / portTICK_PERIOD_MS);

	// Horizontal Scroll
	ssd1306_clear_screen(&dev, false);
	ssd1306_contrast(&dev, 0xff);
	ssd1306_display_text(&dev, center, "Horizontal", 10, false);
	ssd1306_hardware_scroll(&dev, SCROLL_RIGHT);
	vTaskDelay(5000 / portTICK_PERIOD_MS);
	ssd1306_hardware_scroll(&dev, SCROLL_LEFT);
	vTaskDelay(5000 / portTICK_PERIOD_MS);
	ssd1306_hardware_scroll(&dev, SCROLL_STOP);
	
	// Vertical Scroll
	ssd1306_clear_screen(&dev, false);
	ssd1306_contrast(&dev, 0xff);
	ssd1306_display_text(&dev, center, "Vertical", 8, false);
	ssd1306_hardware_scroll(&dev, SCROLL_DOWN);
	vTaskDelay(5000 / portTICK_PERIOD_MS);
	ssd1306_hardware_scroll(&dev, SCROLL_UP);
	vTaskDelay(5000 / portTICK_PERIOD_MS);
	ssd1306_hardware_scroll(&dev, SCROLL_STOP);
	
	// Invert
	ssd1306_clear_screen(&dev, true);
	ssd1306_contrast(&dev, 0xff);
	ssd1306_display_text(&dev, center, "  Good Bye!!", 12, true);
	vTaskDelay(5000 / portTICK_PERIOD_MS);


	// Fade Out
	ssd1306_fadeout(&dev);
	
#if 0
	// Fade Out
	for(int contrast=0xff;contrast>0;contrast=contrast-0x20) {
		ssd1306_contrast(&dev, contrast);
		vTaskDelay(40);
	}
#endif
}
